﻿//*** Guidance generated code ***//
using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.IO;
using System.Linq;
using System.Web.Mvc;
using System.Web.Routing;

using ProjectBase.Core;
using ProjectBase.Core.Web;
using ProjectBase.Mvc;
using ProjectBase.Mvc.Views;

namespace ProjectBase.Mvc.Controllers
{
    /// <summary>
    ///     BASE class for any Controller in the application
    ///     The only 1 default public Action is 'Index'.
    ///     There is also a set of service method (binding, redirecting etc.)
    ///     implemented in 'ProjectBase.Mvc.Controllers' WebController.
    ///
    ///     Put any common AOP Filter here,
    ///     and than evaluate it on any action and controller in the application.
    /// </summary>
    public abstract class WebController<TModel> : Controller, IWebController<TModel>
        where TModel : class, IViewModel
    {
        #region members
        protected const string EmptyTag = "<p></p>";
        AopUrlHelper _aopUrlHelper;
        #endregion members

        #region init
        protected override void Initialize(RequestContext requestContext)
        {
            base.Initialize(requestContext);
            Binders.DefaultBinder = EntityBinder;
            Model.MasterModel.ControllerName = ControllerName;
        }
        #endregion init

        #region class NoView
        class ViewEmpty : IMasterPageLessView
        {
            #region IView Members
            void IView.Render(ViewContext viewContext, TextWriter writer)
            {
                writer.Write(EmptyTag);
            }
            #endregion
        }
        #endregion class NoView

        #region Actions - Index
        /// <summary>
        ///     Starting point of every controller
        /// </summary>
        /// <returns></returns>
        public virtual ActionResult Index()
        {
            return View(Model);
        }
        #endregion Action - Index

        #region properties: Model, ControllerName, Temp collections, Url, Binder
        /// <summary>
        ///     Extends the basic Binder results.
        ///     In comparison with the base implementation,
        ///     this will convert string into 'decimal', 'int', 'short', 'long', 'Guid', 'DateTime'...
        ///     based on the User culture.
        ///     Together with "oposite feature" 'ToDisplay()', which renders value types in culture specific strings,
        ///     provides user friendly input-output conversions.
        /// </summary>
        protected static readonly EntityBinder EntityBinder = new EntityBinder();

        /// <summary>
        ///     Model instance containing specific properties,
        ///     collections and objects for this Controller-View
        /// </summary>
        public virtual TModel Model { get; set; }

        /// <summary>
        ///     Unique Controller name. This property is mostly used for URL creation.
        /// </summary>
        public abstract string ControllerName { get; }

        /// <summary>
        ///     This instance allows to create standard URL
        ///     but is also ready to be extended via some AopUrlHelper
        ///     to keep window dependent info in every link (anchor or form action)
        /// </summary>
        public new virtual IUrlHelper Url
        {
            get
            {
                if (_aopUrlHelper.IsNull())
                {
                    _aopUrlHelper = new AopUrlHelper(base.Url.RequestContext, base.Url.RouteCollection);
                }
                return _aopUrlHelper;
            }
        }
        #endregion properties

        #region Bind
        public virtual bool BindModel<T>(T model, string prefix = null, string[] includeProperties = null, string[] excludeProperties = null, IValueProvider valueProvider = null)
            where T : class
        {
            if (model.IsNull())
            {
                this.PublishError(Constants.Messages.NullValueWasProvided1, "model");
                return false;
            }
            try
            {
                if (!TryUpdateModel(model, prefix, includeProperties, excludeProperties, valueProvider ?? ValueProvider))
                {
                    foreach (var message in ModelState.Where(pair => pair.Value.Errors.Count > 0))
                    {
                        var val = message.Value.Value.Is()
                                      ? message.Value.Value.AttemptedValue
                                      : message.Value.Errors[0].ErrorMessage.Is()
                                            ? message.Value.Errors[0].ErrorMessage
                                            : message.Value.Errors[0].Exception.Message;

                        var entity = model as IPersistentObject;
                        var itemId = entity.IsNull()
                                   ? "N/A"
                                   : entity.IsTransient() ? "new" : entity.IDToDisplay;

                        this.PublishError(Constants.Messages.BindingError4
                            , message.Key
                            , ControllerName
                            , itemId
                            , val);
                    }
                }
                return ModelState.IsValid;
            }
            catch (Exception ex)
            {
                this.PublishFatal(ex);
            }
            return false;
        }
        #endregion Bind

        #region ViewEmpty()
        protected virtual ActionResult EmptyView()
        {
            return View(new ViewEmpty(), null);
        }
        #endregion ViewEmpty()

        #region override HandleUnknownAction
        /// <summary>
        ///     For every unknown action provides redirection to default action
        /// </summary>
        /// <param name = "actionName"></param>
        protected override void HandleUnknownAction(string actionName)
        {
            this.PublishDebug("User {0} tried to access unknown action '{1}' on controller '{2}' "
                .FormatWith(User.Identity.Name, actionName, Model.MasterModel.ControllerName));
            Response.Redirect(Url.Action(Constants.Actions.Default, this.QueryStringToDictionary()));
        }
        #endregion override HandleUnknownAction

        #region override OnAction execution
        /// <summary>
        ///     Base is extended with call to the 'RestoreModel()'.
        ///     Previously stored data (e.g. in the Session()) could be restored there,
        ///     and ready to use in the following Action itself.
        /// </summary>
        /// <param name = "filterContext"></param>
        protected override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            // I. base
            base.OnActionExecuting(filterContext);

            // II. is there previously stored model?
            var storableModel = filterContext.Session()[filterContext.Controller.GetType().FullName] as IStateObject;

            // III. pass that reference to Controller and prepare its IModel
            RestoreModel(filterContext, storableModel);
        }

        /// <summary>
        ///     Extends the base with the call to 'StoreModel()'.
        ///     This is used for example on the entity controller
        ///     to store IFilter criteria in the Session()
        /// </summary>
        /// <param name = "filterContext"></param>
        protected override void OnActionExecuted(ActionExecutedContext filterContext)
        {
            // I. base
            base.OnActionExecuted(filterContext);

            // II. create a reference to be stored in the Session();
            var storableModel = filterContext.Session()[filterContext.Controller.GetType().FullName] as IStateObject;
            if (storableModel.IsNull())
            {
                storableModel = new StateObject();
                filterContext.Session()[filterContext.Controller.GetType().FullName] = storableModel;
            }

            // III. let to controller fill it as needed
            StoreModel(filterContext, storableModel);
        }
        #endregion protected OnActionExecution

        #region override OnResult execution
        /// <summary>
        ///     Clears the Request collection.
        ///     It means, that the Request collection is kept even among many 'innner' RedirectToActions 'Requests'.
        ///     These together can share RequestCollection. For instance Update -> Detail.
        ///     But when the last action apears (returning the ResultView) - collection is cleared.
        /// </summary>
        /// <param name = "filterContext"></param>
        protected override void OnResultExecuted(ResultExecutedContext filterContext)
        {
            base.OnResultExecuted(filterContext);
            if (filterContext.Result is RedirectToRouteResult
             || filterContext.Result is RedirectResult)
            {
                return;
            }
            TempData.Clear();
        }
        #endregion OnResult exucted

        #region Json for ReadControllers
        protected virtual void SetTable<TFilter>(string action, JTable table, TFilter filter,
            IEnumerable<IItemToDisplay> items, bool showRemoveAction)
            where TFilter : class, IFilter
        {
            if (filter.PageCount > 0)
            {
                this.SetTableCaption(filter, action, table);
            }
            if (items.Count() > 0)
            {
                var head = new JRow();
                foreach (var item in items.First().Items)
                {
                    this.SetHeadCell(action, item, head, ControllerName);
                }
                if (showRemoveAction)
                {
                    var remove = new JRowCell
                    {
                        Text = "X",
                        ToolTip = "Remove".GetLocalized(ControllerName),
                    };
                    head.Remove.Add(remove);
                }
                table.Head.Add(head);

                foreach (var item in items)
                {
                    JRow row = this.GetRow(item, showRemoveAction);
                    table.Rows.Add(row);
                }
            }
            else
            {
                table.SetNoResult("empty".GetLocalized());
            }
        }
        #endregion Json for ReadControllers

        #region Store and Restore - Model
        /// <summary>
        ///     Restores the Model from the Information stored in the storage (e.g. Session by default)
        /// </summary>
        /// <param name = "filterContext"></param>
        /// <param name = "storableModel"></param>
        protected virtual void RestoreModel(ActionExecutingContext filterContext, IStateObject storableModel)
        {
            #region Log Debug
            this.PublishDebug("RestoreModel: Restore Model from the SessionStorage() rised");
            #endregion Log Debug
        }

        /// <summary>
        ///     Stores needed Model's properties in the storage (e.g. session by default)
        ///     to be available on return to this controller
        /// </summary>
        /// <param name = "filterContext"></param>
        /// <param name = "storableModel"></param>
        protected virtual void StoreModel(ActionExecutedContext filterContext, IStateObject storableModel)
        {
            #region Log Debug
            this.PublishDebug("StoreModel: Store Model to the SessionStorage() rised");
            #endregion Log Debug
        }
        #endregion Store and Restore - Model

        #region IMessageHolder
        public virtual Messages Messages { get; set; }
        #endregion
    }
}